<!--
 * @Date: 2025-01-03 14:48:26
 * @Author: LiSong
 * @LastEditors: LiSong
 * @LastEditTime: 2025-03-27 11:33:26
 * @Description: 表单渲染 
-->
<template>
  <div class="fp-render-container">
    <VFormRender
      v-if="renderState.dataLoaded"
      ref="renderRef"
      :formJson="renderState.formJson"
      :busiConfig="busiConfig"
      :axiosConfig="axiosConfig"
      :enumsConfig="enumsConfig"
      @formChange="renderFormChange"
      @loadedMounted="renderFormLoaded"
      @buttonClick="(...params) => emits('buttonClick', ...params)"
      @btnGroupLeftClick="(...params) => emits('btnGroupLeftClick', ...params)"
      @btnGroupRightClick="(...params) => emits('btnGroupRightClick', ...params)"
      @onTableButtonsClick="(...params) => emits('onTableButtonsClick', ...params)"
    >
      <template v-for="slotItem in renderState.slotJson"
        :key="slotItem.value"
        #[slotItem.value]
      >
        <component
          :="slotAttrs"
          :is="slotModules[slotItem.value]"
          :ref="(el) => { slotRefs[slotItem.value] = el }"
          :mark="slotItem.value"
          :slotsData="renderState.slotsData"
          :slotDataHouse="renderState.slotsData"
          @vue:mounted="setCompMounted"
          @setFormItemValue="setFormItemValue"
        >
        </component>
      </template>
    </VFormRender>
  </div>
</template>

<script setup>
import XEUtils from 'xe-utils';
import ISUtils from '@/utils/is-utils.js'

import { useRoute } from 'vue-router';
import { defAxios } from '@/utils/http';
import { ElMessage } from 'element-plus';
import { computed, nextTick, reactive, watch, useAttrs } from 'vue';

import { useUserStore } from '@/stores/modules';
import { EnumsDataPools } from '@/enums';

import { baseInfo } from '@fp/config/form-pojo.js'
import { slotModules } from '@fp/slots'
import * as PressUtils from '@/utils/press.js'


const attrs = useAttrs()

const emits = defineEmits([
  'loaded',
  'buttonClick',
  'btnGroupLeftClick',
  'btnGroupRightClick',
  'onTableButtonsClick',
])

const props = defineProps({
  // 业务ID
  busiId: { type: String, default: 'preview' },
  // 是否预览：true-是（全局禁用）false-否
  preview: { type: Boolean, default: false },
  // 自动加载
  autoLoad: { type: Boolean, default: true },
  // 是否全部禁用
  disabled: { type: Boolean, default: false },
  // 基础信息
  baseInfo: { type: Object, default: () => ({}) },
  // 业务信息
  busiInfo: { type: Object, default: () => ({}) },
  // 表单配置
  formJson: { type: Object },
  // 表单插槽配置
  slotJson: { type: Array, default: () => [] },
  // 表单编码
  formCode: { type: String, default: '' },
  // 回显数据
  formData: { type: Object, default: () => ({}) },
  /*
    表单类型：可用于控制表单插槽中的一些业务逻辑，以及一些显示隐藏效果
  */
  formType: { type: String, default: 'view' },
  // 附件业务类型
  busiType: { type: String, default: '' },
  // tabs控件选中页签
  tabActive: { type: String },
  // 插槽的属性
  slotProps: { type: Object, default: () => ({}) },
  // 开启插槽加载完成事件
  openSlotLoaded: { type: Boolean, default: false },
  // tabs组件默认选中的页签
  defaultTabActiveName: {type: String},
})

// 当前路由
const route = useRoute()

// 插槽的Ref
const slotRefs = ref({})
// 表单渲染组件的Ref
const renderRef = ref(null)
// 数据集
const renderState = reactive({
  // 加载中
  loading: false,
  // 基本信息
  baseInfo: {},
  // 表单配置
  formJson: {},
  // 插槽配置
  slotJson: [],
  // 业务信息
  busiInfo: props.busiInfo,
  // 表单数据
  formData: props.formData,
  // 插槽数据
  slotsData: {},
  // 数据加载完成
  dataLoaded: false,
  // 插槽加载完成次数
  slotMountedNum: 0,
  slotMountedMap: []
})

// 业务属性配置
const busiConfig = reactive({
  userInfo: useUserStore().userInfo,
  busiInfo: renderState.busiInfo,
  dataHouse: renderState.formData,
  slotsData: renderState.slotsData,
  routeQuery: route.query,
  executeFunc: {},
  renderAttrs: { ...attrs },
  slotDataHouse: renderState.slotsData,
})
// 轻松属性配置
const axiosConfig = reactive({
  request: defAxios
})
// 常量属性配置
const enumsConfig = reactive({
  dataPools: EnumsDataPools
})

// 插槽的一些属性
const slotAttrs = computed(() => {
  return {
    // 业务ID
    busiId: renderState.formData.proId || props.busiId,
    // 附件业务类型
    busiType: props.busiType || import.meta.env.VITE_FILE_BUSI_TYPE || 'ET001',
    // 是否预览
    preview: props.preview,
    // 是否禁用
    disabled: props.disabled,
    // 表单编码
    formCode: props.formCode,
    // 表单类型（可用于控制插槽的一些操作）
    formType: props.formType,
    // 业务信息
    busiInfo: renderState.busiInfo,
    // 基本信息
    baseInfo: renderState.baseInfo,
    // 表单数据
    formData: renderState.formData,
    // 用于判断是否在表单中
    useInForm: true,
    // 插槽数据
    // slotsData: renderState.slotsData,
    // 按钮编码
    buttonCode: renderState.formData.buttonCode || '',
    // 设置字段的值的方法
    setFormValue: setFormItemValue,
    // 插槽数据（冗余兼容）
    // slotDataHouse: renderState.slotsData,
  }
})

/**
 * 加载表单渲染
 */
const loadRender = async () => {
  renderState.loading = true
  renderState.dataLoaded = false
  renderState.slotMountedNum = 0
  if (props.formJson) {
    renderState.formJson = XEUtils.clone(props.formJson, true)
    renderState.slotJson = XEUtils.clone(props.slotJson, true)
  } else {
    // 如果没有表单编码，则直接返回
    if (!props.formCode) {
      emits('loaded', XEUtils.clone(renderState, true))
      // console.warn('表单编码不能为空！')
      return
    } else {
      // 请求接口获取配置信息
      const data = await reqGetRenderInfo(props.formCode)
      // 封装数据
      const { prdFormContent } = data
      if (prdFormContent) {
        try {
          const { structureJson } = prdFormContent
          // 是否为压缩数据
          if (PressUtils.isPressData(structureJson)) {
            renderState.formJson = PressUtils.decompressData(structureJson)
          } else {
            renderState.formJson = JSON.parse(structureJson || '{}')
          }
          renderState.slotJson = JSON.parse(prdFormContent.slotJson || '[]')
        } catch (error) {
          console.error('解析表单数据失败', error)
        }
      }
      // 设置基本信息
      const base = { ...data, ...prdFormContent || {} }
      Object.keys(baseInfo).forEach(key => {
        renderState.baseInfo[key] = base[key] || baseInfo[key]
      })
    }
  }
  // 设置插槽数据
  setSlotsData()
  // 设置业务数据
  setBusiConfig()
  renderState.dataLoaded = true
  // 设置渲染数据
  setRenderData()
  renderState.loading = false
  console.log('加载表单数据！')
}

/**
 * 表单渲染加载完成
 */
const renderFormLoaded = () => {
  setFormItemStatus()
  setTabWidgetActive()
  renderState.loading = false
  if (!props.openSlotLoaded) {
    emits('loaded', XEUtils.clone(renderState, true))
  }
}

/**
 * 表单数据变更触发
 * @param {String} name 修改字段名称
 * @param {Any} nv 字段修改后的内容
 * @param {Any} ov 字段修改前的内容
 * @param {Object} fm 表单数据
 */
const renderFormChange = (name, nv, ov, data) => {
  nextTick(() => {
    // 如果没有渲染组件不触发
    if (!renderRef.value) return
    // 加载中不触发
    if (renderState.loading) return
    // 数据合并
    renderState.formData = { ...renderState.formData, ...data }
  })
}

/**
 * 设置插槽数据
 */
const setSlotsData = () => {
  let result = {}
  const { slotJson } = renderState
  slotJson.forEach(item => result[item.value] = {})
  renderState.slotsData = result
  renderState.slotDataHouse = result
}
/**
 * 设置表单数据
 */
const setRenderData = () => {
  const { formData, slotJson } = props
  if (XEUtils.isEmpty(formData)) return
  renderState.formData = { ...formData }
  nextTick(() => {
    renderRef.value.setFormData(XEUtils.clone(formData, true), true)
    const resData = renderRef.value.getFormData(false)
    // 去除插槽的字段数据
    Object.keys(resData).forEach(key => {
      if (slotJson.find(item => item.value === key)) {
        delete resData[key]
      }
    })
    renderState.formData = { ...renderState.formData, ...resData }
  })
}
/**
 * 设置业务数据
 */
const setBusiConfig = () => {
  // 业务配置
  busiConfig.userInfo = useUserStore().userInfo
  busiConfig.dataHouse = renderState.formData
  busiConfig.executeFunc = { getTabChildFeilds }
}
/**
 * 设置插槽组件加载完成次数
 */
const setCompMounted = () => {
  renderState.slotMountedNum++
  if (props.openSlotLoaded && renderState.slotJson.length === renderState.slotMountedNum && !renderState.loading) {
    emits('loaded', XEUtils.clone(renderState, true))
  } 
}
/**
 * 设置单个表单字段的内容
 * 
 * @param name 表单字段名称
 * @param value 表单字段内容
 */
const setFormItemValue = (name, value) => {
  renderRef.value.setFieldValue(name, value)
}
/**
 * 设置表单项的状态
 */
const setFormItemStatus = () => {
  nextTick(() => {
    if (props.disabled || props.preview) renderRef.value.disableForm()
  })
}
/**
 * 设置tab默认选中
 * 
 * @param activeName tab选中的面板名称
 */
const setTabWidgetActive = (active, tabName) => {
  active = active || props.tabActive || props.defaultTabActiveName
  if (!active) return
  const { getContainerWidgets, getWidgetRef } = renderRef.value;
  // 获取布局容器
  const containerWidgets = getContainerWidgets();
  // 获取tab容器的widget
  const tabWidgets = containerWidgets.filter((item) => item.type === 'tab');
  if (tabWidgets.length === 0) return
  if (tabWidgets.length === 1) {
    const tabRef = getWidgetRef(tabWidgets[0].name);
    tabRef.activeTabName = active;
  } else {
    console.log('存在多个tab！') 
  }
}

/**
 * 根据名称获取表单中的渲染组件
 * 
 * @param name 渲染组件名称
 */
const getWidgetRef = (name) => {
  return renderRef.value.getWidgetRef(name)
}
/**
 * 获取渲染组件数据
 */
const getRenderData = async () => {
  const { getFormData, getFieldWidgets } = renderRef.value

  const fieldWidgets = getFieldWidgets()
  const formRenderData = getFormData(false)

  // 合并表单数据
  const finalFormData = { ...renderState.formData, ...formRenderData }

  const dataArray = []
  const dataConfig = {}
  const dataValues = {}

  // 组装数据
  const formWidgets = fieldWidgets.filter(item => item.type !== 'slot')
  for (let item of formWidgets) {
    const { name } = item
    let value = finalFormData[item.name]
    value = ISUtils.isNotEmpty(value) ? value : ''
     
    const dataItem = {
      // type: item.type,
      label: item.label,
      value,
      dataField: item.dataField
    }
    dataArray.push(dataItem)
    dataConfig[name] = dataItem
    dataValues[name] = value
  }

  // 获取插槽返回数据
  let slotsData = {}
  if (slotRefs.value) {
    for (let item of renderState.slotJson) {
      const slotRef = slotRefs.value[item.value]
      if (!slotRef) continue;
      if (!slotRef.getCompData) continue;
      slotsData = { ...slotsData, ...(await slotRef.getCompData()) }
    }
  }

  return {
    dataObj: dataConfig,
    formData: dataValues,
    slotsData,
    dataArray,
  }

}

/**
 * 获取指定tab下的字段信息
 * 
 * @param tabKey tab的名称
 */
const getTabChildFeilds = (tabKey) => {
  const { widgetList } = renderState.formJson
  let tabItem = null
  let fieldInfo = {
    allFields:[],
    editFields: [],
    hiddenFields:[],
    disabledFields:[],
    requiredFields:[]
  }
  let tabsWidget = []
  
  function getFieldWidget (widgetList, fieldObj) {
    if (!fieldObj) {
      fieldObj = { allFields:[], editFields: [], hiddenFields:[], disabledFields:[], requiredFields:[] }
    }
    widgetList.forEach(item => {
      if (item.formItemFlag) {
        const { hidden, disabled, required, dataField, readonly } = item.options
        fieldObj.allFields.push(dataField)
        if (hidden) { fieldObj.hiddenFields.push(dataField) }
        if (disabled) { fieldObj.disabledFields.push(dataField) }
        if (required) { fieldObj.requiredFields.push(dataField) }
        if (!(disabled && readonly)) { fieldObj.editFields.push(dataField) }
      }
      let childList = []
      if (item.type === 'grid') {
        childList = item.cols
      } else {
        childList = item.widgetList || []
      }
      if (childList && childList.length) {
        fieldObj = getFieldWidget(childList, fieldObj)
      }
    })
    return fieldObj
  }

  tabsWidget = widgetList.fliter(item => item.type === 'tab')
  if (tabKey) {
    tabItem = tabsWidget.find(item => item.name === tabKey)
    fieldInfo = getFieldWidget(tabItem.widgetList)
  }

  return { tabItem, fieldInfo }
}

/**
 * 根据表单编码请求获取表单配置信息
 * 
 * @param formCode 表单编码
 */
const reqGetRenderInfo = (formCode) => {
  return new Promise(resolve => {
    defAxios({
      url: '/prdFormInfo/queryPrdFormInfoListByFormCode',
      method: 'GET',
      params: { formCode }
    }).then(res => {
      if (res.code === 200) {
        resolve(res.data)
      } else {
        resolve({});
        console.error(res.msg)
      }
    }).catch(error => {
      resolve({});
      console.error('获取配置数据失败', error)
    })
  })
}

/**
 * 校验单个字段的内容
 * 
 * @param fieldName 字段名称
 */
const validateField = (fieldName) => {
  const { validateField: validateFormField, getFieldWidgets } = renderRef.value
  const fieldWidgets = getFieldWidgets();
  const fieldWidgetObj = fieldWidgets.find(item => item.name === fieldName)
  return new Promise(resolve => {
    validateFormField(fieldName, (valid) => {
      if(!valid) setTabWidgetActive(fieldWidgetObj.tabInfo.name)
      resolve(valid)
    })
  })

}

/**
 * 校验表单数据
 * 
 * @param onlySlotValid 只校验插槽 默认开启
 */
const validateFormData = async (onlySlotValid=false) =>  {
  const { getFieldWidgets } = renderRef.value

  const fieldWidgets = getFieldWidgets();
  const fieldWidgetObj = {};
  const slotsWidgetObj = {};

  // 拆分字段和插槽
  fieldWidgets.forEach(item => {
    if (item.type === 'slot') {
      slotsWidgetObj[item.name] = item
    } else {
      fieldWidgetObj[item.name] = item;
    }
  })

  let baseValid = true
  let slotValid = true
  if (onlySlotValid) {
    slotValid = await validateSlotFields({ slotsWidgetObj })
    return slotValid
  } else {
    baseValid = await validateBaseFields({ fieldWidgetObj })
    if (!baseValid) return baseValid
    slotValid = await validateSlotFields({ slotsWidgetObj })
    return baseValid && slotValid;
  }
}

/**
 * 校验基本字段数据
 * 
 * @param {Object} param
 * @param {Object} param.fieldWidgetObj 字段配置对象
 * @returns {Promise<Boolean>} true-校验通过 false-校验不通过
 */
const validateBaseFields = ({ fieldWidgetObj={} }) => {
  const { validateForm, getFieldWidgets } = renderRef.value
  if (XEUtils.isEmpty(fieldWidgetObj)) {
    const fieldWidgets = getFieldWidgets();
    fieldWidgets.filter(item => item.type !== 'slot').forEach(item => fieldWidgetObj[item.name] = item)
  }
  const tabWidgetInfo = {}
  return new Promise(resolve => {
    validateForm((valid, invalidFields) => {
      if (valid) {
        resolve(true);
      } else {
        for (let key of Object.keys(invalidFields)) {
          const tabInfo = fieldWidgetObj[key]?.tabInfo;
          if (!tabInfo) continue;
          let tabFields = tabWidgetInfo[tabInfo.name]
          if (tabFields) {
            tabFields.fields.push()
          } else {
            tabFields = {
              name: tabInfo.name,
              label: tabInfo.label,
              fields: [ key ]
            }
          }
          // 重新赋值
          tabWidgetInfo[tabInfo.name] = tabFields
        }
        const tabWidgetValues = Object.values(tabWidgetInfo)
        if (tabWidgetValues && tabWidgetValues.length) {
          let firstTab = tabWidgetValues[0]
          setTabWidgetActive(firstTab.name)
          ElMessage.error(`${firstTab.label}：请检查必填项，以及未通过校验项！`);
        }
        resolve(false);
      }
    })
  })
}

/**
 * 校验插槽字段数据
 * @param {Object} param0  {slotsWidgetObj={}}
 * @returns {Promise<boolean>}  true:校验通过,false:校验不通过
 */
const validateSlotFields = ({ slotsWidgetObj={} }) => {
  const { getFieldWidgets } = renderRef.value
  if (!XEUtils.isEmpty(slotsWidgetObj)) {
    const fieldWidgets = getFieldWidgets();
    fieldWidgets.filter(item => item.type === 'slot').forEach(item => slotsWidgetObj[item.name] = item)
  }
  return new Promise(async resolve => {
    let flag = true
    if (slotRefs.value) {
      for (let slotItem of Object.values(slotsWidgetObj)) {
        const { name: slotName } = slotItem
        const slotRef = slotRefs.value[slotName]
        if (!slotRef) continue;
        if (!slotRef.validateData) continue;
        const validateInfo = await slotRef.validateData()
        const { valid, message } = validateInfo
        flag = valid
        let messageType = validateInfo.type || 'error' 
        if (!valid) {
          if (messageType === 'warning') {
            ElMessage.warning(message);
          } else {
            setTabWidgetActive(slotItem.tabInfo.name)
            ElMessage.error(message);
          }
          break;
        }
      }
    }
    resolve(flag);
  })
}

/**
 * 校验插槽数据
 * 
 * @param slotName 插槽名称
 * @param extParams 额外参数
 */
const validateSlotsData = async (slotName, extParams) => {
  extParams = { ...{ checkRequired: true, checkLength: true }, ...(extParams || {}) }
  const { getFieldWidgets } = renderRef.value;
  const fieldWidgets = getFieldWidgets();
  const slotsWidgets = fieldWidgets.filter(item => item.type === 'slot')
  let flag = true;
  if (!slotName) {
    flag = await validateSlotFields({ slotsWidgetObj: slotsWidgets })
  } else {
    const slotItem = slotsWidgets.find(item => item.name === slotName)
    if (!slotItem) {
      console.warn(`组件${slotName}不存在！`)
    } else {
      const slotRef = slotRefs[slotItem.name]
      if (!slotRef) return true
      if (!slotRef.validateData) {
        console.warn(`组件${slotItem.name}暂未提供validateData方法！`)
      } else {
        const validateResult = await slotRef.validateData(extParams)
        flag = validateResult.valid
        if (!flag) {
          setTabWidgetActive(slotItem.tabInfo.name);
          ElMessage.error(`${validateResult.message}`);
          return flag
        }
      }
    }
  }
  return flag
}

// 监听表单数据，更新表单渲染
watch(
  [
    () => props.formJson,
    () => props.slotJson,
    () => props.formCode
  ],
  () => {
    setTimeout(() => { loadRender() }, 100)
  },
  { deep: true, immediate: props.autoLoad }
)

// 监听插槽数据，更新表单渲染的业务数据配置
watch(
  () => renderState.slotsData,
  (nv) => {
    busiConfig.slotsData = nv
    busiConfig.slotDataHouse = nv
  }
)

// /**
//  * 监听表单数据
//  * 设置表单回显数据
//  */
// watch(
//   () => props.formData,
//   () => {
//     if (XEUtils.isEmpty(props.formData)) return
//     setRenderData()
//   },
//   { deep: true, immediate: false }
// )

defineExpose({
  loadRender,
  // 兼容适配旧版本，新用法请使用 loadRender
  loadFormInfo: loadRender,
  getWidgetRef,
  validateField,
  getRenderData,
  // 兼容适配旧版本，新用法请使用 getRenderData
  getRenderFormData: getRenderData,
  setFormItemValue,
  validateFormData,
  validateSlotsData,
  validateBaseFields,
  validateSlotFields,
})

</script>

<style lang="scss" scoped>
@use '@fp/styles/form-render.scss' as *;
</style>